/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package playboxclient;

import java.awt.Desktop;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.StringReader;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import sun.misc.BASE64Decoder;


/**
 *
 * @author programmer
 */
public class PlayEngine {
    private String m_gamename;
    private String m_gamepath;
    //private String m_playboxid; //Box (asym. Key)
    private String m_username;
    private String m_userpw;
    private byte[] m_plainPrivKeyData;
    
    
    /**
     * 
     * @param _gamename Name des Spiels das aufgerufen werden soll
     * @param _username Name des Spielers, der sich angemeldet hat
     * @param _pw Passwort
     */
    PlayEngine(String _gamename, String _username, String _pw) {
        this.m_gamename = _gamename;
        this.m_gamepath = System.getenv("HOME")+"/Playbox/"+this.m_gamename;//+"/game.enc";
        this.m_username = _username;
        this.m_userpw = _pw;
        
    }
    
    /**
     * Hauptmethode der Klasse PlayEngine. Enth&auml;lt Haupt-Logik und ruft
     * alle privaten Methoden auf. Aufgaben: Online-Prüfung, ob Spiel einem gehört,
     * wenn Encrypted Game downloaden wenn notwendig, entschlüsseln und Datei
     * &ouml;ffnen.
     */
    public void playGame() throws InvalidAlgorithmParameterException {
        //String game = "/home/programmer/Playbox/"+this.m_gamename+"/game.enc";
        
        //Online-Check für Spiel
        boolean gameowned = checkOnlineGame();
        
        if (gameowned == false) {
            JOptionPane.showMessageDialog(new JDialog(), "Sie sind nicht Besitzer dieses Spiels. \n PlayBox wird geschlossen!!", "Kritischer Fehler", JOptionPane.ERROR_MESSAGE);
            System.exit(0); 
        }
        
        //Check ob auf Festplatte vorhanden, wenn nicht - download
        checkAndDownloadGame();
        
        //Krypto-Operationen
        // - entschluessle Lic asym --> lege plaintext key ab
        // - entschluessle game.enc sym --> lege game ab
        //Treten Fehler auf, wird Console geschlossen und alle Klartexte gelöscht.
        if (!doCrypto()) {
            String game = "game";
            String gamePath = System.getenv("HOME")+"/Playbox";
            File path = new File(gamePath);
            String[] listelocalgames = null;
            
            if (path.isDirectory()) {
                listelocalgames = path.list();
            } //if (isDirectory)
            
            File todeletefile;
            System.out.println(listelocalgames[1]); 
            for (int i=0; i< listelocalgames.length;i++) {
                todeletefile = new File(gamePath+"/"+listelocalgames[i]+"/"+game);
                if (todeletefile.exists())
                    System.out.println(todeletefile.getAbsolutePath());
            } //for
            System.exit(0);
        } //if (doCrypto)
        
        //öffnet das Game
        String filename = System.getenv("HOME")+"/Playbox/"+this.m_gamename+"/game";
        try {
            File file = new File(filename);
            try {
                Desktop.getDesktop().open(file);
            } catch (IOException ex) {
                Logger.getLogger(PlayBoxClientView.class.getName()).log(Level.SEVERE, null, ex);
            } //catch
            
        } catch (Exception e) {
            System.out.println("Error");
        } //catch
    }
    
    /**
     * Decryption-Engine. Entschlüsselt das Game-File. Ruft das Entschlüsseln
     * des Lizenz-File auf. Bricht ab wenn das Entschlüsseln der Lizenz irgendwelche
     * Fehler produzieren.
     * @return true, wenn alles OK
     */
    private boolean doCrypto() throws InvalidAlgorithmParameterException{
        try {
            System.out.println("entschluessle lic");
            if (decryptLicense()) {
                System.out.println("entschluessle game");
                if (decryptGame()){
                    return true;
                } //if
                else
                    return false;
            } //if
            else
                return false;    
        } catch (FileNotFoundException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchPaddingException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeyException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalBlockSizeException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        } catch (BadPaddingException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return true;
    }
    
    /**
     * Entschlüsselt mit dem Private-Key die verschlüsselte Lizenz. Der Schlüssel
     * muss im PKCS8-Format vorhanden sein. Der extrahierte Schlüssel wird nicht
     * gespeichert, er wird mittels einer membervariable im RAM gehalten.
     * 
     * @return
     * @throws FileNotFoundException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException 
     */
    
    private boolean decryptLicense() throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        RandomAccessFile raf = new RandomAccessFile(System.getenv("HOME")+"/Playbox/playboxkey.pk8", "r");
        byte[] buf = new byte[(int)raf.length()];
        raf.readFully(buf);
        raf.close();
        PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(buf);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privKey = kf.generatePrivate(kspec);

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE,privKey);
        
        File toprivdec = new File(this.m_gamepath+"/lic.enc");
 
        byte[] toprivdec_byte = null;
        
        toprivdec_byte=getBytesFromFile(toprivdec);
        byte[] plainData = cipher.doFinal(toprivdec_byte);
        this.m_plainPrivKeyData = plainData;
        
        if (this.m_plainPrivKeyData  == null)
            return false;
        else
            return true;
    }
    
    /**
     * Entschlüsselt mit Hilfe des aus der Lizenz erhaltenen Schlüssel das
     * symmetrisch verschlüsselte Spiel.
     * 
     * @return true, wenn alles OK
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException 
     */
    private boolean decryptGame() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
        File encfilename = new File(this.m_gamepath + "/game.enc");
        byte[] enc_byte = null;
        
        try {
            enc_byte = getBytesFromFile(encfilename);
        } catch (IOException ex) {
            Logger.getLogger(PlayEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        //File keyfilename = new File(this.m_gamepath + "/lic");
        byte[] key_byte = this.m_plainPrivKeyData;
        
        
        SecretKeySpec key = new SecretKeySpec(key_byte, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        System.out.println(key.getEncoded().length);
        
        byte[] iv = new byte[]
        {
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
        };

        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);

        int ctLength = enc_byte.length;
        byte[] decrypted = new byte[ctLength];
        
        //Cipher vorbereiten
        cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
        
        //entschlüsseln und schreiben des Plain-Games
        decrypted=cipher.doFinal(enc_byte);  
        File gamefilefordelete = new File(this.m_gamepath+"/game");
        
        try {
            String FilePath = this.m_gamepath+"/game";
            FileOutputStream fos = new FileOutputStream(FilePath);
            fos.write(decrypted);
            fos.close();
            gamefilefordelete.deleteOnExit();
            return true;
        }
        catch (Exception e){
            return false;
        }
    }
    
    /**
     * Prüfen, ob das Spiel im Besitz des Anwenders ist.
     * 
     * @return true, Spiel gehört dem User
     */
    
    private boolean checkOnlineGame() {
        ArrayList<String> game_list = new ArrayList<String>();
        
        //WebService vorbereiten
        playboxws.PlayBoxWS_Service service = new playboxws.PlayBoxWS_Service();
        playboxws.PlayBoxWS port = service.getPlayBoxWSPort();
        
        //rufe Methode aus WS auf
        String str = port.getMyGames(getUsername());
        
        //ist etwas zurückgekommen...?
        if (str != null) {
            System.out.println(str);
            try {
                StringReader inStream = new StringReader(str);
                InputSource inSource = new InputSource(inStream);

                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                DocumentBuilder db = dbf.newDocumentBuilder();
                Document doc = db.parse(inSource);
                doc.getDocumentElement().normalize();
                System.out.println(doc.getDocumentElement().getTextContent());

                NodeList nodes = doc.getElementsByTagName("name");
                for (int i=0; i< nodes.getLength(); i++) {
                    Element element = (Element) nodes.item(i);
                    game_list.add(element.getTextContent());
                    System.out.println(element.getTextContent());
                } //for
            
            } catch (Exception e) {
                System.out.println("Fehler:" + e.getMessage());
            }

            return (game_list.contains(this.m_gamename)?true:false);
    }//if
        else
            return false;
}//fkt
    
    /**
     * liefert den Usernamen
     * @return username
     */
    private String getUsername() {
        return this.m_username;
    }

    /**
     * Prüft, ob das Spiel lokal verfügbar ist, wenn nicht, wird es online
     * nachgeladen
     */
    private void checkAndDownloadGame() {
        File Game = new File(this.m_gamepath+"/game.enc");
        File Lic = new File(this.m_gamepath+"/lic.enc");
        File Ordner = new File(this.m_gamepath);
        boolean success=true;
        String WSResult;
        ArrayList<String> game_list = new ArrayList<String>();
        ArrayList<String> lic_list = new ArrayList<String>();
        
        //WS vorbereiten
        playboxws.PlayBoxWS_Service service = new playboxws.PlayBoxWS_Service();
        playboxws.PlayBoxWS port = service.getPlayBoxWSPort();
        
        // Ordner vorhanden?
        if (!Ordner.exists())
            success = (new File(this.m_gamepath)).mkdir();
        if (!success)
            System.out.println("Fehler beim Ordner erzeugen");
        
        //Spiel bereits vorhanden?
        if (!Game.exists()) {
            System.out.println(m_gamename + "" + PlayBoxClientView.getGameId(m_gamename) );
            WSResult = port.getGame(this.m_username, PlayBoxClientView.getGameId(m_gamename)+1); //contentid+1, da mysql mit 1 beginnt
            System.out.println(WSResult);
            System.out.println(PlayBoxClientView.getGameId(this.m_gamename)+1);
            //xmlsuche nach encGame
            try {
                game_list = this.parseResult(WSResult, "encGame");
                System.out.println(WSResult);
                FileOutputStream fileoutstream = new FileOutputStream(Game);
                
                //Decode Base64-String und schreibe resultierendes in Datei
                //verschlüsseltes Spiel
                BASE64Decoder decoder = new BASE64Decoder();
                byte[] decodedBytes = decoder.decodeBuffer(game_list.get(0));
                fileoutstream.write(decodedBytes);
                fileoutstream.close();

            }
            catch (Exception e) {
                System.out.println("Fehler read out base64:" + e.getMessage());
            }
            
            //extrahiere base64codiertes file
            //speicher sym.verschl. chiffrat
        }//if (Game.exist)
        
        if (!Lic.exists())  {
            WSResult = port.getLicense(this.m_username, PlayBoxClientView.getGameId(m_gamename)+1);
            System.out.println(WSResult);
            
            //xmlsuche nach "license"
            //extrahiere base64codiertes file
            //speichere pub-keyverschluesseltes file
            try {
                lic_list = this.parseResult(WSResult, "license");
                FileOutputStream fileoutstream = new FileOutputStream(Lic);
                
                //decode base64-String und schreibe Ergebnis in Datei
                BASE64Decoder decoder = new BASE64Decoder();
                byte[] decodedBytes = decoder.decodeBuffer(lic_list.get(0));
                fileoutstream.write(decodedBytes);
                fileoutstream.close();
            } catch (Exception e){
                System.out.println("Fehler read out base64:" + e.getMessage());
            }
        }//if (lic.exists)
            
    }
    
    /**
     * XML-String wird geparst und das Ergebnis wird als ArrayList zurückgeliefert.
     * 
     * @param _result
     * @param _tag
     * @return 
     */
    private ArrayList parseResult(String _result,String _tag) {
        ArrayList list =new ArrayList();
        
        if (_result != null) {
            System.out.println(_result);
            try {
                StringReader inStream = new StringReader(_result);
                InputSource inSource = new InputSource(inStream);

                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                DocumentBuilder db = dbf.newDocumentBuilder();
                Document doc = db.parse(inSource);
                doc.getDocumentElement().normalize();
                System.out.println(doc.getDocumentElement().getTextContent());

                NodeList nodes = doc.getElementsByTagName(_tag);
                for (int i=0; i< nodes.getLength(); i++) {
                    Element element = (Element) nodes.item(i);
                    list.add(element.getTextContent());
                    System.out.println(element.getTextContent());
                } //for
            } catch (Exception e) {
                System.out.println("Fehler:" + e.getMessage());
            }
            
            return list;
        } //if (result!=null)
        else
            return null;
    }
    
    /**
     * liest ein File ein und gibt byte-Array zurück
     * 
     * @param file
     * @return
     * @throws IOException 
     */
    private byte[] getBytesFromFile(File file) throws IOException {
        InputStream is = new FileInputStream(file);

        // Get the size of the file
        long length = file.length();

        // You cannot create an array using a long type.
        // It needs to be an int type.
        // Before converting to an int type, check
        // to ensure that file is not larger than Integer.MAX_VALUE.
        if (length > Integer.MAX_VALUE) {
            // File is too large
        }

        // Create the byte array to hold the data
        byte[] bytes = new byte[(int)length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
            offset += numRead;
        }

        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file "+file.getName());
        }

        // Close the input stream and return bytes
        is.close();
        return bytes;
    }
}
